function mxFreehand(graph)
{
	// Graph must have a container
	var svgElement = (graph.view != null && graph.view.canvas != null) ? graph.view.canvas.ownerSVGElement : null;
	
	if (graph.container == null || svgElement == null)
	{
		return;
	}
	
	//Code inspired by https://stackoverflow.com/questions/40324313/svg-smooth-freehand-drawing
	var bufferSize = mxFreehand.prototype.NORMAL_SMOOTHING;
	var path = null;
	var partPathes = [];
	var strPath;
	var drawPoints = [];
	var lastPart;
	var closedPath = false; 
	var autoClose = true;
	var autoInsert = false;
	var autoScroll = false;
	var openFill = false;
	var buffer = []; // Contains the last positions of the mouse cursor
	var enabled = false;
	var stopClickEnabled = true

	this.setClosedPath = function(isClosed)//TODO add closed settings
	{
		closedPath = isClosed;
	};
	
	this.setAutoClose = function(isAutoClose)//TODO add auto closed settings
	{
		autoClose = isAutoClose;
	};

	this.setAutoInsert = function(value)
	{
		autoInsert = value;
	};
	
	this.setAutoScroll = function(value)
	{
		autoScroll = value;
	};
		
	this.setOpenFill = function(value)
	{
		openFill = value;
	};
	
	this.setStopClickEnabled = function(enabled)
	{
		stopClickEnabled = enabled;
	};
	
	this.setSmoothing = function(smoothing)//TODO add smoothing settings
	{
		bufferSize = smoothing;
	};
	
	var setEnabled = function(isEnabled)
	{
		enabled = isEnabled;
		graph.getRubberband().setEnabled(!isEnabled);
		graph.graphHandler.setSelectEnabled(!isEnabled);
		graph.graphHandler.setMoveEnabled(!isEnabled);
		graph.container.style.cursor = (isEnabled) ? 'crosshair' : '';
		graph.fireEvent(new mxEventObject('freehandStateChanged'));
	};
	
	this.startDrawing = function()
	{
		setEnabled(true);
	}
	
	this.isDrawing = function()
	{
		return enabled;
	};
	
	var endPath = mxUtils.bind(this, function(e)
	{
	    if (path)
	    {
			var lastLength = lastPart.length;
		
	    	// Click stops drawing
	    	var doStop = stopClickEnabled && drawPoints.length > 0 &&
	    		lastPart != null && lastPart.length < 2;
	    	
			if (!doStop)
			{
				drawPoints.push.apply(drawPoints, lastPart);
			}
			
	        lastPart = [];
			drawPoints.push(null);
	        partPathes.push(path);
	        path = null;
	        
			if (doStop || autoInsert)
			{
				this.stopDrawing();
			}
			
			if (autoInsert && lastLength >= 2)
			{
				this.startDrawing();
			}
			
	        mxEvent.consume(e);
	    }
	});
	
	this.createStyle = function(stencil)
	{
		return mxConstants.STYLE_SHAPE + '=' + stencil + ';fillColor=none;';
	};
	
	this.stopDrawing = function() 
	{
	    if (partPathes.length > 0) 
	    {
	        var maxX = drawPoints[0].x, minX = drawPoints[0].x, maxY = drawPoints[0].y, minY = drawPoints[0].y;
	        
	        for (var i = 1; i < drawPoints.length; i++) 
	        {
	        	if (drawPoints[i] == null) continue;
	        	
	        	maxX = Math.max(maxX, drawPoints[i].x);
	        	minX = Math.min(minX, drawPoints[i].x);
	        	maxY = Math.max(maxY, drawPoints[i].y);
	        	minY = Math.min(minY, drawPoints[i].y);
	        }
	        
	        var w = maxX - minX, h = maxY - minY;
	        
	        if (w > 0 && h > 0)
	        {
		        var xScale = 100 / w;
		        var yScale = 100 / h;
		        
		        drawPoints.map(function(p) 
		        {
		        	if (p == null) return p;
		        	
		        	p.x = (p.x - minX) * xScale;
		        	p.y = (p.y - minY) * yScale;
		        	return p;
		        });
		        
		        //toFixed(2) to reduce size of output
		        var drawShape = '<shape strokewidth="inherit"><foreground>';
		        
		        var start = 0;
		        
		        for (var i = 0; i < drawPoints.length; i++) 
		        {
		        	var p = drawPoints[i];

		        	if (p == null)
	        		{
		        		var tmpClosedPath = false;
				        var startP =  drawPoints[start], endP = drawPoints[i - 1];
				        
				        if (!closedPath && autoClose)
				        {
					        var xdiff = startP.x - endP.x, ydiff = startP.y - endP.y;
					        var startEndDist = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
					        
					        tmpClosedPath = startEndDist <= graph.tolerance;
				        }
				        
				        if (closedPath || tmpClosedPath) 
			        	{
				        	drawShape += '<line x="'+ startP.x.toFixed(2) + '" y="' + startP.y.toFixed(2) + '"/>';
			        	}
				        
		        		drawShape += '</path>' + ((openFill || closedPath || tmpClosedPath)? '<fillstroke/>' : '<stroke/>');
		        		start = i + 1;
	        		}
		        	else if (i == start)
	        		{
		        		drawShape += '<path><move x="'+ p.x.toFixed(2) + '" y="' + p.y.toFixed(2) + '"/>'
	        		}
		        	else
		        	{
		        		drawShape += '<line x="'+ p.x.toFixed(2) + '" y="' + p.y.toFixed(2) + '"/>';
		        	}
		        }
		        
		        drawShape += '</foreground></shape>';
		        
                var style = this.createStyle('stencil(' + Graph.compress(drawShape) + ')');
                var s = graph.view.scale;
            	var tr = graph.view.translate;
            	
                var cell = new mxCell('', new mxGeometry(minX / s - tr.x, minY / s - tr.y, w / s, h / s), style);
                cell.vertex = 1;
                
                graph.model.beginUpdate();
                try
				{
                	cell = graph.addCell(cell);
				}
                finally
				{
                	graph.model.endUpdate();
				}
                
                graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [cell]));
                graph.fireEvent(new mxEventObject('freehandInserted', 'cell', cell));
                //While mouse is down, we cannot select!
                setTimeout(function(){graph.setSelectionCells([cell]); }, 10);
	        }

	        for (var i = 0; i < partPathes.length; i++)
	        {
	        	partPathes[i].parentNode.removeChild(partPathes[i]);
	        }
	        
	        path = null;
	        partPathes = [];
	        drawPoints = [];
	    }

        setEnabled(false);
	};

	// Stops all interactions if freehand is enabled
	graph.addListener(mxEvent.FIRE_MOUSE_EVENT, mxUtils.bind(this, function(sender, evt)
	{
		var evtName = evt.getProperty('eventName');
		var me = evt.getProperty('event');
		
		if (evtName == mxEvent.MOUSE_MOVE && enabled)
		{
			if (me.sourceState != null)
			{
				me.sourceState.setCursor('crosshair');
			}
			
			me.consume();
		}
	}));
	
	// Used to retrieve default styles
	var edge = new mxCell();
	edge.edge = true;
	
	// Implements a listener for hover and click handling
	graph.addMouseListener(
	{
	    mouseDown: mxUtils.bind(this, function(sender, me)
	    {
			var e = me.getEvent();
			
			if (!enabled || mxEvent.isPopupTrigger(e) || mxEvent.isMultiTouchEvent(e))
			{
				return;
			}
			
			var defaultStyle = graph.getCurrentCellStyle(edge);
			var strokeWidth = parseFloat(graph.currentVertexStyle[mxConstants.STYLE_STROKEWIDTH] || 1);
			strokeWidth = Math.max(1, strokeWidth * graph.view.scale);
		    path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
		    path.setAttribute('fill', 'none');
		    path.setAttribute('stroke', mxUtils.getValue(graph.currentVertexStyle, mxConstants.STYLE_STROKECOLOR,
				mxUtils.getValue(defaultStyle, mxConstants.STYLE_STROKECOLOR, '#000')));
		    path.setAttribute('stroke-width', strokeWidth);
		    
		    if (graph.currentVertexStyle[mxConstants.STYLE_DASHED] == '1')
		    {
		    	var dashPattern = graph.currentVertexStyle[mxConstants.STYLE_DASH_PATTERN] || '3 3';
		    	
		    	dashPattern = dashPattern.split(' ').map(function(p)
				{
		    		return parseFloat(p) * strokeWidth;
				}).join(' ');
		    	path.setAttribute('stroke-dasharray', dashPattern);
		    }
		    
		    buffer = [];
		    var pt = getMousePosition(e);
		    appendToBuffer(pt);
		    strPath = 'M' + pt.x + ' ' + pt.y;
		    drawPoints.push(pt);
		    lastPart = [];
		    path.setAttribute('d', strPath);
		    svgElement.appendChild(path);

			me.consume();
	    }),
	    mouseMove: mxUtils.bind(this, function(sender, me)
	    {
		    if (path) 
		    {
	    		var e = me.getEvent();
				var pt = getMousePosition(e);
		        appendToBuffer(pt);
		        updateSvgPath();
				
				if (autoScroll)
				{
					var tr = graph.view.translate;
					graph.scrollRectToVisible(new mxRectangle(pt.x - tr.x, pt.y - tr.y).grow(20));
				}
				
				me.consume();
		    }
	    }),
	    mouseUp: mxUtils.bind(this, function(sender, me)
	    {
			if (path) 
			{
				endPath(me.getEvent());
				me.consume();
			}
	    })	
	});

	var getMousePosition = function (e) 
	{
	    return mxUtils.convertPoint(graph.container, mxEvent.getClientX(e), mxEvent.getClientY(e));
	};

	var appendToBuffer = function (pt) 
	{
	    buffer.push(pt);
	    
	    while (buffer.length > bufferSize) 
	    {
	        buffer.shift();
	    }
	};

	// Calculate the average point, starting at offset in the buffer
	var getAveragePoint = function (offset) 
	{
	    var len = buffer.length;
	    
	    if (len % 2 === 1 || len >= bufferSize) 
	    {
	        var totalX = 0;
	        var totalY = 0;
	        var pt, i;
	        var count = 0;
	        
	        for (i = offset; i < len; i++) 
	        {
	            count++;
	            pt = buffer[i];
	            totalX += pt.x;
	            totalY += pt.y;
	        }
	        
	        return {
	            x: totalX / count,
	            y: totalY / count
	        }
	    }
	    
	    return null;
	};

	var updateSvgPath = function () 
	{
	    var pt = getAveragePoint(0);

	    if (pt) 
	    {
	        // Get the smoothed part of the path that will not change
	        strPath += ' L' + pt.x + ' ' + pt.y;
	        drawPoints.push(pt);
	        // Get the last part of the path (close to the current mouse position)
	        // This part will change if the mouse moves again
	        var tmpPath = '';
	        lastPart = [];
	        
	        for (var offset = 2; offset < buffer.length; offset += 2) 
	        {
	            pt = getAveragePoint(offset);
	            tmpPath += ' L' + pt.x + ' ' + pt.y;
	            lastPart.push(pt);
	        }

	        // Set the complete current path coordinates
	        path.setAttribute('d', strPath + tmpPath);
	    }
	};
};

mxFreehand.prototype.NO_SMOOTHING = 1;
mxFreehand.prototype.MILD_SMOOTHING = 4;
mxFreehand.prototype.NORMAL_SMOOTHING = 8;
mxFreehand.prototype.VERY_SMOOTH_SMOOTHING = 12;
mxFreehand.prototype.SUPER_SMOOTH_SMOOTHING = 16;
mxFreehand.prototype.HYPER_SMOOTH_SMOOTHING = 20;
